using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

using Pegasus.Diagnostics;
using Pegasus.Log4Net;

namespace Pegasus.Data.SqlClient
{
	/// <summary>
	/// This class is used the handler the database notification.  This functionality only works
	/// with SQL Server 2005.
	/// </summary>
	internal class SqlDatabaseNotification : IDisposable
	{
		// Local Instance Values
		private ILog m_log = LogManager.GetLogger( typeof( SqlDatabaseNotification ) );
		private object m_lock = new object();
		private string m_sql;
		private SqlConnection m_connection;
		private SqlCommand m_command;
		private OnChangeEventHandler m_internalNotificationHandler;
		private List<OnChangeEventHandler> m_handlerList = new List<OnChangeEventHandler>();

		/// <summary>
		/// Initializes a new instance of the <see cref="T:SqlDatabaseNotification"/> class.
		/// </summary>
		/// <param name="connectionString">The connection string.</param>
		/// <param name="sql">The SQL.</param>
		/// <param name="eventHandler">The event handler.</param>
		public SqlDatabaseNotification( string connectionString, string sql, OnChangeEventHandler eventHandler )
		{
			// Check Parameters
			ParamCode.AssertNotEmpty( connectionString, "connectionString" );
			ParamCode.AssertNotEmpty( sql, "sql" );
			ParamCode.AssertNotNull( eventHandler, "eventHandler" );

			m_sql = sql;

			m_internalNotificationHandler = new OnChangeEventHandler( Database_OnDataChanged );
			m_handlerList.Add( eventHandler );

			// Create the connection and command objects
			m_connection = new SqlConnection( connectionString );
			m_connection.Open();

			m_command = new SqlCommand( sql, m_connection );
			
			// Bind the command object to a sql dependency object.
			BindCommand();
		}

		/// <summary>
		/// Gets the SQL statement that is monitoring the database.
		/// </summary>
		/// <value>The SQL.</value>
		public string SQL
		{
			get
			{
				return m_sql;
			}
		}

		/// <summary>
		/// Gets the number of handlers that are registed for this object.
		/// </summary>
		/// <value>The handlers.</value>
		public int Handlers
		{
			get
			{
				lock( m_lock )
				{
					return m_handlerList.Count;
				}
			}
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			lock( m_lock )
			{
				if( m_connection != null )
				{
					m_connection.Close();
					m_connection = null;
				}
			}
		}

		/// <summary>
		/// Adds the specified event handler.
		/// </summary>
		/// <param name="eventHandler">The event handler.</param>
		public void Add( OnChangeEventHandler eventHandler )
		{
			// Check Parameters
			ParamCode.AssertNotNull( eventHandler, "eventHandler" );

			lock( m_lock )
			{
				m_handlerList.Add( eventHandler );
			}
		}

		/// <summary>
		/// Removes the specified event handler.
		/// </summary>
		/// <param name="eventHandler">The event handler.</param>
		public void Remove( OnChangeEventHandler eventHandler )
		{
			// Check Parameters
			ParamCode.AssertNotNull( eventHandler, "eventHandler" );

			lock( m_lock )
			{
				m_handlerList.Remove( eventHandler );
			}
		}

		/// <summary>
		/// Binds the command object to an SqlDependency object.
		/// </summary>
		private void BindCommand()
		{
			lock( m_lock )
			{
				// Make sure the command object does not already have a notification object associated with it.
				m_command.Notification = null;

				// Create and bind the SqlDependency object to the command object.
				SqlDependency dependency = new SqlDependency( m_command );
				dependency.OnChange += m_internalNotificationHandler;

				// Execute the command
				m_command.ExecuteScalar();
			}
		}

		/// <summary>
		/// Handles the OnDataChanged event of the Database control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="T:System.Data.SqlClient.SqlNotificationEventArgs"/> instance containing the event data.</param>
		private void Database_OnDataChanged( object sender, SqlNotificationEventArgs args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( sender, "sender" );
			ParamCode.Assert( sender is SqlDependency, "sender" );
			ParamCode.AssertNotNull( args, "args" );

			lock( m_lock )
			{
				// Remove the handler, since it is only good for a single notification.
				SqlDependency dependency = (SqlDependency) sender;
				dependency.OnChange -= m_internalNotificationHandler;

				foreach( OnChangeEventHandler eventHandler in m_handlerList )
				{
					// If the client throws then just catch, report, and continue.
					try
					{
						eventHandler( sender, args );
					}
					catch( Exception e )
					{
						m_log.Error( "OnChangeEventHandler through an exception", e );
					}
				}

				// Rebind the command object to a sql dependency object.
				BindCommand();
			}
		}
	}
}
